/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.net;

import java.util.Enumeration;
import java.util.Vector;
import java.util.List;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;
import java.net.InetAddress;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.PrivilegedAction;
import java.security.AccessController;
import java.security.Security;
import java.io.Serializable;
import java.io.ObjectStreamField;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;
import sun.net.util.IPAddressUtil;
import sun.net.RegisteredDomain;
import sun.net.PortConfig;
import sun.security.util.SecurityConstants;
import sun.security.util.Debug;


/**
 * This class represents access to a network via sockets.
 * A SocketPermission consists of a
 * host specification and a set of "actions" specifying ways to
 * connect to that host. The host is specified as
 * <pre>
 *    host = (hostname | IPv4address | iPv6reference) [:portrange]
 *    portrange = portnumber | -portnumber | portnumber-[portnumber]
 * </pre>
 * The host is expressed as a DNS name, as a numerical IP address,
 * or as "localhost" (for the local machine).
 * The wildcard "*" may be included once in a DNS name host
 * specification. If it is included, it must be in the leftmost
 * position, as in "*.sun.com".
 * <p>
 * The format of the IPv6reference should follow that specified in <a
 * href="http://www.ietf.org/rfc/rfc2732.txt"><i>RFC&nbsp;2732: Format
 * for Literal IPv6 Addresses in URLs</i></a>:
 * <pre>
 *    ipv6reference = "[" IPv6address "]"
 * </pre>
 * For example, you can construct a SocketPermission instance
 * as the following:
 * <pre>
 *    String hostAddress = inetaddress.getHostAddress();
 *    if (inetaddress instanceof Inet6Address) {
 *        sp = new SocketPermission("[" + hostAddress + "]:" + port, action);
 *    } else {
 *        sp = new SocketPermission(hostAddress + ":" + port, action);
 *    }
 * </pre>
 * or
 * <pre>
 *    String host = url.getHost();
 *    sp = new SocketPermission(host + ":" + port, action);
 * </pre>
 * <p>
 * The <A HREF="Inet6Address.html#lform">full uncompressed form</A> of
 * an IPv6 literal address is also valid.
 * <p>
 * The port or portrange is optional. A port specification of the
 * form "N-", where <i>N</i> is a port number, signifies all ports
 * numbered <i>N</i> and above, while a specification of the
 * form "-N" indicates all ports numbered <i>N</i> and below.
 * The special port value {@code 0} refers to the entire <i>ephemeral</i>
 * port range. This is a fixed range of ports a system may use to
 * allocate dynamic ports from. The actual range may be system dependent.
 * <p>
 * The possible ways to connect to the host are
 * <pre>
 * accept
 * connect
 * listen
 * resolve
 * </pre>
 * The "listen" action is only meaningful when used with "localhost" and
 * means the ability to bind to a specified port.
 * The "resolve" action is implied when any of the other actions are present.
 * The action "resolve" refers to host/ip name service lookups.
 * <P>
 * The actions string is converted to lowercase before processing.
 * <p>As an example of the creation and meaning of SocketPermissions,
 * note that if the following permission:
 *
 * <pre>
 *   p1 = new SocketPermission("puffin.eng.sun.com:7777", "connect,accept");
 * </pre>
 *
 * is granted to some code, it allows that code to connect to port 7777 on
 * {@code puffin.eng.sun.com}, and to accept connections on that port.
 *
 * <p>Similarly, if the following permission:
 *
 * <pre>
 *   p2 = new SocketPermission("localhost:1024-", "accept,connect,listen");
 * </pre>
 *
 * is granted to some code, it allows that code to
 * accept connections on, connect to, or listen on any port between
 * 1024 and 65535 on the local host.
 *
 * <p>Note: Granting code permission to accept or make connections to remote
 * hosts may be dangerous because malevolent code can then more easily
 * transfer and share confidential data among parties who may not
 * otherwise have access to the data.
 *
 * @author Marianne Mueller
 * @author Roland Schemers
 * @serial exclude
 * @see java.security.Permissions
 * @see SocketPermission
 */

public final class SocketPermission extends Permission
    implements java.io.Serializable {

  private static final long serialVersionUID = -7204263841984476862L;

  /**
   * Connect to host:port
   */
  private final static int CONNECT = 0x1;

  /**
   * Listen on host:port
   */
  private final static int LISTEN = 0x2;

  /**
   * Accept a connection from host:port
   */
  private final static int ACCEPT = 0x4;

  /**
   * Resolve DNS queries
   */
  private final static int RESOLVE = 0x8;

  /**
   * No actions
   */
  private final static int NONE = 0x0;

  /**
   * All actions
   */
  private final static int ALL = CONNECT | LISTEN | ACCEPT | RESOLVE;

  // various port constants
  private static final int PORT_MIN = 0;
  private static final int PORT_MAX = 65535;
  private static final int PRIV_PORT_MAX = 1023;
  private static final int DEF_EPH_LOW = 49152;

  // the actions mask
  private transient int mask;

  /**
   * the actions string.
   *
   * @serial
   */

  private String actions; // Left null as long as possible, then
  // created and re-used in the getAction function.

  // hostname part as it is passed
  private transient String hostname;

  // the canonical name of the host
  // in the case of "*.foo.com", cname is ".foo.com".

  private transient String cname;

  // all the IP addresses of the host
  private transient InetAddress[] addresses;

  // true if the hostname is a wildcard (e.g. "*.sun.com")
  private transient boolean wildcard;

  // true if we were initialized with a single numeric IP address
  private transient boolean init_with_ip;

  // true if this SocketPermission represents an invalid/unknown host
  // used for implies when the delayed lookup has already failed
  private transient boolean invalid;

  // port range on host
  private transient int[] portrange;

  private transient boolean defaultDeny = false;

  // true if this SocketPermission represents a hostname
  // that failed our reverse mapping heuristic test
  private transient boolean untrusted;
  private transient boolean trusted;

  // true if the sun.net.trustNameService system property is set
  private static boolean trustNameService;

  private static Debug debug = null;
  private static boolean debugInit = false;

  // lazy initializer
  private static class EphemeralRange {

    static final int low = initEphemeralPorts("low", DEF_EPH_LOW);
    static final int high = initEphemeralPorts("high", PORT_MAX);
  }

  ;

  static {
    Boolean tmp = java.security.AccessController.doPrivileged(
        new sun.security.action.GetBooleanAction("sun.net.trustNameService"));
    trustNameService = tmp.booleanValue();
  }

  private static synchronized Debug getDebug() {
    if (!debugInit) {
      debug = Debug.getInstance("access");
      debugInit = true;
    }
    return debug;
  }

  /**
   * Creates a new SocketPermission object with the specified actions.
   * The host is expressed as a DNS name, or as a numerical IP address.
   * Optionally, a port or a portrange may be supplied (separated
   * from the DNS name or IP address by a colon).
   * <p>
   * To specify the local machine, use "localhost" as the <i>host</i>.
   * Also note: An empty <i>host</i> String ("") is equivalent to "localhost".
   * <p>
   * The <i>actions</i> parameter contains a comma-separated list of the
   * actions granted for the specified host (and port(s)). Possible actions are
   * "connect", "listen", "accept", "resolve", or
   * any combination of those. "resolve" is automatically added
   * when any of the other three are specified.
   * <p>
   * Examples of SocketPermission instantiation are the following:
   * <pre>
   *    nr = new SocketPermission("www.catalog.com", "connect");
   *    nr = new SocketPermission("www.sun.com:80", "connect");
   *    nr = new SocketPermission("*.sun.com", "connect");
   *    nr = new SocketPermission("*.edu", "resolve");
   *    nr = new SocketPermission("204.160.241.0", "connect");
   *    nr = new SocketPermission("localhost:1024-65535", "listen");
   *    nr = new SocketPermission("204.160.241.0:1024-65535", "connect");
   * </pre>
   *
   * @param host the hostname or IPaddress of the computer, optionally including a colon followed by
   * a port or port range.
   * @param action the action string.
   */
  public SocketPermission(String host, String action) {
    super(getHost(host));
    // name initialized to getHost(host); NPE detected in getHost()
    init(getName(), getMask(action));
  }


  SocketPermission(String host, int mask) {
    super(getHost(host));
    // name initialized to getHost(host); NPE detected in getHost()
    init(getName(), mask);
  }

  private void setDeny() {
    defaultDeny = true;
  }

  private static String getHost(String host) {
    if (host.equals("")) {
      return "localhost";
    } else {
            /* IPv6 literal address used in this context should follow
             * the format specified in RFC 2732;
             * if not, we try to solve the unambiguous case
             */
      int ind;
      if (host.charAt(0) != '[') {
        if ((ind = host.indexOf(':')) != host.lastIndexOf(':')) {
                    /* More than one ":", meaning IPv6 address is not
                     * in RFC 2732 format;
                     * We will rectify user errors for all unambiguious cases
                     */
          StringTokenizer st = new StringTokenizer(host, ":");
          int tokens = st.countTokens();
          if (tokens == 9) {
            // IPv6 address followed by port
            ind = host.lastIndexOf(':');
            host = "[" + host.substring(0, ind) + "]" +
                host.substring(ind);
          } else if (tokens == 8 && host.indexOf("::") == -1) {
            // IPv6 address only, not followed by port
            host = "[" + host + "]";
          } else {
            // could be ambiguous
            throw new IllegalArgumentException("Ambiguous" +
                " hostport part");
          }
        }
      }
      return host;
    }
  }

  private int[] parsePort(String port)
      throws Exception {

    if (port == null || port.equals("") || port.equals("*")) {
      return new int[]{PORT_MIN, PORT_MAX};
    }

    int dash = port.indexOf('-');

    if (dash == -1) {
      int p = Integer.parseInt(port);
      return new int[]{p, p};
    } else {
      String low = port.substring(0, dash);
      String high = port.substring(dash + 1);
      int l, h;

      if (low.equals("")) {
        l = PORT_MIN;
      } else {
        l = Integer.parseInt(low);
      }

      if (high.equals("")) {
        h = PORT_MAX;
      } else {
        h = Integer.parseInt(high);
      }
      if (l < 0 || h < 0 || h < l) {
        throw new IllegalArgumentException("invalid port range");
      }

      return new int[]{l, h};
    }
  }

  /**
   * Returns true if the permission has specified zero
   * as its value (or lower bound) signifying the ephemeral range
   */
  private boolean includesEphemerals() {
    return portrange[0] == 0;
  }

  /**
   * Initialize the SocketPermission object. We don't do any DNS lookups
   * as this point, instead we hold off until the implies method is
   * called.
   */
  private void init(String host, int mask) {
    // Set the integer mask that represents the actions

    if ((mask & ALL) != mask) {
      throw new IllegalArgumentException("invalid actions mask");
    }

    // always OR in RESOLVE if we allow any of the others
    this.mask = mask | RESOLVE;

    // Parse the host name.  A name has up to three components, the
    // hostname, a port number, or two numbers representing a port
    // range.   "www.sun.com:8080-9090" is a valid host name.

    // With IPv6 an address can be 2010:836B:4179::836B:4179
    // An IPv6 address needs to be enclose in []
    // For ex: [2010:836B:4179::836B:4179]:8080-9090
    // Refer to RFC 2732 for more information.

    int rb = 0;
    int start = 0, end = 0;
    int sep = -1;
    String hostport = host;
    if (host.charAt(0) == '[') {
      start = 1;
      rb = host.indexOf(']');
      if (rb != -1) {
        host = host.substring(start, rb);
      } else {
        throw new
            IllegalArgumentException("invalid host/port: " + host);
      }
      sep = hostport.indexOf(':', rb + 1);
    } else {
      start = 0;
      sep = host.indexOf(':', rb);
      end = sep;
      if (sep != -1) {
        host = host.substring(start, end);
      }
    }

    if (sep != -1) {
      String port = hostport.substring(sep + 1);
      try {
        portrange = parsePort(port);
      } catch (Exception e) {
        throw new
            IllegalArgumentException("invalid port range: " + port);
      }
    } else {
      portrange = new int[]{PORT_MIN, PORT_MAX};
    }

    hostname = host;

    // is this a domain wildcard specification
    if (host.lastIndexOf('*') > 0) {
      throw new
          IllegalArgumentException("invalid host wildcard specification");
    } else if (host.startsWith("*")) {
      wildcard = true;
      if (host.equals("*")) {
        cname = "";
      } else if (host.startsWith("*.")) {
        cname = host.substring(1).toLowerCase();
      } else {
        throw new
            IllegalArgumentException("invalid host wildcard specification");
      }
      return;
    } else {
      if (host.length() > 0) {
        // see if we are being initialized with an IP address.
        char ch = host.charAt(0);
        if (ch == ':' || Character.digit(ch, 16) != -1) {
          byte ip[] = IPAddressUtil.textToNumericFormatV4(host);
          if (ip == null) {
            ip = IPAddressUtil.textToNumericFormatV6(host);
          }
          if (ip != null) {
            try {
              addresses =
                  new InetAddress[]
                      {InetAddress.getByAddress(ip)};
              init_with_ip = true;
            } catch (UnknownHostException uhe) {
              // this shouldn't happen
              invalid = true;
            }
          }
        }
      }
    }
  }

  /**
   * Convert an action string to an integer actions mask.
   *
   * @param action the action string
   * @return the action mask
   */
  private static int getMask(String action) {

    if (action == null) {
      throw new NullPointerException("action can't be null");
    }

    if (action.equals("")) {
      throw new IllegalArgumentException("action can't be empty");
    }

    int mask = NONE;

    // Use object identity comparison against known-interned strings for
    // performance benefit (these values are used heavily within the JDK).
    if (action == SecurityConstants.SOCKET_RESOLVE_ACTION) {
      return RESOLVE;
    } else if (action == SecurityConstants.SOCKET_CONNECT_ACTION) {
      return CONNECT;
    } else if (action == SecurityConstants.SOCKET_LISTEN_ACTION) {
      return LISTEN;
    } else if (action == SecurityConstants.SOCKET_ACCEPT_ACTION) {
      return ACCEPT;
    } else if (action == SecurityConstants.SOCKET_CONNECT_ACCEPT_ACTION) {
      return CONNECT | ACCEPT;
    }

    char[] a = action.toCharArray();

    int i = a.length - 1;
    if (i < 0) {
      return mask;
    }

    while (i != -1) {
      char c;

      // skip whitespace
      while ((i != -1) && ((c = a[i]) == ' ' ||
          c == '\r' ||
          c == '\n' ||
          c == '\f' ||
          c == '\t')) {
        i--;
      }

      // check for the known strings
      int matchlen;

      if (i >= 6 && (a[i - 6] == 'c' || a[i - 6] == 'C') &&
          (a[i - 5] == 'o' || a[i - 5] == 'O') &&
          (a[i - 4] == 'n' || a[i - 4] == 'N') &&
          (a[i - 3] == 'n' || a[i - 3] == 'N') &&
          (a[i - 2] == 'e' || a[i - 2] == 'E') &&
          (a[i - 1] == 'c' || a[i - 1] == 'C') &&
          (a[i] == 't' || a[i] == 'T')) {
        matchlen = 7;
        mask |= CONNECT;

      } else if (i >= 6 && (a[i - 6] == 'r' || a[i - 6] == 'R') &&
          (a[i - 5] == 'e' || a[i - 5] == 'E') &&
          (a[i - 4] == 's' || a[i - 4] == 'S') &&
          (a[i - 3] == 'o' || a[i - 3] == 'O') &&
          (a[i - 2] == 'l' || a[i - 2] == 'L') &&
          (a[i - 1] == 'v' || a[i - 1] == 'V') &&
          (a[i] == 'e' || a[i] == 'E')) {
        matchlen = 7;
        mask |= RESOLVE;

      } else if (i >= 5 && (a[i - 5] == 'l' || a[i - 5] == 'L') &&
          (a[i - 4] == 'i' || a[i - 4] == 'I') &&
          (a[i - 3] == 's' || a[i - 3] == 'S') &&
          (a[i - 2] == 't' || a[i - 2] == 'T') &&
          (a[i - 1] == 'e' || a[i - 1] == 'E') &&
          (a[i] == 'n' || a[i] == 'N')) {
        matchlen = 6;
        mask |= LISTEN;

      } else if (i >= 5 && (a[i - 5] == 'a' || a[i - 5] == 'A') &&
          (a[i - 4] == 'c' || a[i - 4] == 'C') &&
          (a[i - 3] == 'c' || a[i - 3] == 'C') &&
          (a[i - 2] == 'e' || a[i - 2] == 'E') &&
          (a[i - 1] == 'p' || a[i - 1] == 'P') &&
          (a[i] == 't' || a[i] == 'T')) {
        matchlen = 6;
        mask |= ACCEPT;

      } else {
        // parse error
        throw new IllegalArgumentException(
            "invalid permission: " + action);
      }

      // make sure we didn't just match the tail of a word
      // like "ackbarfaccept".  Also, skip to the comma.
      boolean seencomma = false;
      while (i >= matchlen && !seencomma) {
        switch (a[i - matchlen]) {
          case ',':
            seencomma = true;
            break;
          case ' ':
          case '\r':
          case '\n':
          case '\f':
          case '\t':
            break;
          default:
            throw new IllegalArgumentException(
                "invalid permission: " + action);
        }
        i--;
      }

      // point i at the location of the comma minus one (or -1).
      i -= matchlen;
    }

    return mask;
  }

  private boolean isUntrusted()
      throws UnknownHostException {
    if (trusted) {
      return false;
    }
    if (invalid || untrusted) {
      return true;
    }
    try {
      if (!trustNameService && (defaultDeny ||
          sun.net.www.URLConnection.isProxiedHost(hostname))) {
        if (this.cname == null) {
          this.getCanonName();
        }
        if (!match(cname, hostname)) {
          // Last chance
          if (!authorized(hostname, addresses[0].getAddress())) {
            untrusted = true;
            Debug debug = getDebug();
            if (debug != null && Debug.isOn("failure")) {
              debug.println("socket access restriction: proxied host " + "(" + addresses[0] + ")"
                  + " does not match " + cname + " from reverse lookup");
            }
            return true;
          }
        }
        trusted = true;
      }
    } catch (UnknownHostException uhe) {
      invalid = true;
      throw uhe;
    }
    return false;
  }

  /**
   * attempt to get the fully qualified domain name
   */
  void getCanonName()
      throws UnknownHostException {
    if (cname != null || invalid || untrusted) {
      return;
    }

    // attempt to get the canonical name

    try {
      // first get the IP addresses if we don't have them yet
      // this is because we need the IP address to then get
      // FQDN.
      if (addresses == null) {
        getIP();
      }

      // we have to do this check, otherwise we might not
      // get the fully qualified domain name
      if (init_with_ip) {
        cname = addresses[0].getHostName(false).toLowerCase();
      } else {
        cname = InetAddress.getByName(addresses[0].getHostAddress()).
            getHostName(false).toLowerCase();
      }
    } catch (UnknownHostException uhe) {
      invalid = true;
      throw uhe;
    }
  }

  private transient String cdomain, hdomain;

  private boolean match(String cname, String hname) {
    String a = cname.toLowerCase();
    String b = hname.toLowerCase();
    if (a.startsWith(b) &&
        ((a.length() == b.length()) || (a.charAt(b.length()) == '.'))) {
      return true;
    }
    if (cdomain == null) {
      cdomain = RegisteredDomain.getRegisteredDomain(a);
    }
    if (hdomain == null) {
      hdomain = RegisteredDomain.getRegisteredDomain(b);
    }

    return cdomain.length() != 0 && hdomain.length() != 0
        && cdomain.equals(hdomain);
  }

  private boolean authorized(String cname, byte[] addr) {
    if (addr.length == 4) {
      return authorizedIPv4(cname, addr);
    } else if (addr.length == 16) {
      return authorizedIPv6(cname, addr);
    } else {
      return false;
    }
  }

  private boolean authorizedIPv4(String cname, byte[] addr) {
    String authHost = "";
    InetAddress auth;

    try {
      authHost = "auth." +
          (addr[3] & 0xff) + "." + (addr[2] & 0xff) + "." +
          (addr[1] & 0xff) + "." + (addr[0] & 0xff) +
          ".in-addr.arpa";
      // Following check seems unnecessary
      // auth = InetAddress.getAllByName0(authHost, false)[0];
      authHost = hostname + '.' + authHost;
      auth = InetAddress.getAllByName0(authHost, false)[0];
      if (auth.equals(InetAddress.getByAddress(addr))) {
        return true;
      }
      Debug debug = getDebug();
      if (debug != null && Debug.isOn("failure")) {
        debug.println("socket access restriction: IP address of " + auth + " != " + InetAddress
            .getByAddress(addr));
      }
    } catch (UnknownHostException uhe) {
      Debug debug = getDebug();
      if (debug != null && Debug.isOn("failure")) {
        debug.println("socket access restriction: forward lookup failed for " + authHost);
      }
    }
    return false;
  }

  private boolean authorizedIPv6(String cname, byte[] addr) {
    String authHost = "";
    InetAddress auth;

    try {
      StringBuffer sb = new StringBuffer(39);

      for (int i = 15; i >= 0; i--) {
        sb.append(Integer.toHexString(((addr[i]) & 0x0f)));
        sb.append('.');
        sb.append(Integer.toHexString(((addr[i] >> 4) & 0x0f)));
        sb.append('.');
      }
      authHost = "auth." + sb.toString() + "IP6.ARPA";
      //auth = InetAddress.getAllByName0(authHost, false)[0];
      authHost = hostname + '.' + authHost;
      auth = InetAddress.getAllByName0(authHost, false)[0];
      if (auth.equals(InetAddress.getByAddress(addr))) {
        return true;
      }
      Debug debug = getDebug();
      if (debug != null && Debug.isOn("failure")) {
        debug.println("socket access restriction: IP address of " + auth + " != " + InetAddress
            .getByAddress(addr));
      }
    } catch (UnknownHostException uhe) {
      Debug debug = getDebug();
      if (debug != null && Debug.isOn("failure")) {
        debug.println("socket access restriction: forward lookup failed for " + authHost);
      }
    }
    return false;
  }


  /**
   * get IP addresses. Sets invalid to true if we can't get them.
   */
  void getIP()
      throws UnknownHostException {
    if (addresses != null || wildcard || invalid) {
      return;
    }

    try {
      // now get all the IP addresses
      String host;
      if (getName().charAt(0) == '[') {
        // Literal IPv6 address
        host = getName().substring(1, getName().indexOf(']'));
      } else {
        int i = getName().indexOf(":");
        if (i == -1) {
          host = getName();
        } else {
          host = getName().substring(0, i);
        }
      }

      addresses =
          new InetAddress[]{InetAddress.getAllByName0(host, false)[0]};

    } catch (UnknownHostException uhe) {
      invalid = true;
      throw uhe;
    } catch (IndexOutOfBoundsException iobe) {
      invalid = true;
      throw new UnknownHostException(getName());
    }
  }

  /**
   * Checks if this socket permission object "implies" the
   * specified permission.
   * <P>
   * More specifically, this method first ensures that all of the following
   * are true (and returns false if any of them are not):
   * <ul>
   * <li> <i>p</i> is an instanceof SocketPermission,
   * <li> <i>p</i>'s actions are a proper subset of this
   * object's actions, and
   * <li> <i>p</i>'s port range is included in this port range. Note:
   * port range is ignored when p only contains the action, 'resolve'.
   * </ul>
   *
   * Then {@code implies} checks each of the following, in order,
   * and for each returns true if the stated condition is true:
   * <ul>
   * <li> If this object was initialized with a single IP address and one of <i>p</i>'s
   * IP addresses is equal to this object's IP address.
   * <li>If this object is a wildcard domain (such as *.sun.com), and
   * <i>p</i>'s canonical name (the name without any preceding *)
   * ends with this object's canonical host name. For example, *.sun.com
   * implies *.eng.sun.com.
   * <li>If this object was not initialized with a single IP address, and one of this
   * object's IP addresses equals one of <i>p</i>'s IP addresses.
   * <li>If this canonical name equals <i>p</i>'s canonical name.
   * </ul>
   *
   * If none of the above are true, {@code implies} returns false.
   *
   * @param p the permission to check against.
   * @return true if the specified permission is implied by this object, false if not.
   */
  public boolean implies(Permission p) {
    int i, j;

    if (!(p instanceof SocketPermission)) {
      return false;
    }

    if (p == this) {
      return true;
    }

    SocketPermission that = (SocketPermission) p;

    return ((this.mask & that.mask) == that.mask) &&
        impliesIgnoreMask(that);
  }

  /**
   * Checks if the incoming Permission's action are a proper subset of
   * the this object's actions.
   * <P>
   * Check, in the following order:
   * <ul>
   * <li> Checks that "p" is an instanceof a SocketPermission
   * <li> Checks that "p"'s actions are a proper subset of the
   * current object's actions.
   * <li> Checks that "p"'s port range is included in this port range
   * <li> If this object was initialized with an IP address, checks that
   * one of "p"'s IP addresses is equal to this object's IP address.
   * <li> If either object is a wildcard domain (i.e., "*.sun.com"),
   * attempt to match based on the wildcard.
   * <li> If this object was not initialized with an IP address, attempt
   * to find a match based on the IP addresses in both objects.
   * <li> Attempt to match on the canonical hostnames of both objects.
   * </ul>
   *
   * @param that the incoming permission request
   * @return true if "permission" is a proper subset of the current object, false if not.
   */
  boolean impliesIgnoreMask(SocketPermission that) {
    int i, j;

    if ((that.mask & RESOLVE) != that.mask) {

      // check simple port range
      if ((that.portrange[0] < this.portrange[0]) ||
          (that.portrange[1] > this.portrange[1])) {

        // if either includes the ephemeral range, do full check
        if (this.includesEphemerals() || that.includesEphemerals()) {
          if (!inRange(this.portrange[0], this.portrange[1],
              that.portrange[0], that.portrange[1])) {
            return false;
          }
        } else {
          return false;
        }
      }
    }

    // allow a "*" wildcard to always match anything
    if (this.wildcard && "".equals(this.cname)) {
      return true;
    }

    // return if either one of these NetPerm objects are invalid...
    if (this.invalid || that.invalid) {
      return compareHostnames(that);
    }

    try {
      if (this.init_with_ip) { // we only check IP addresses
        if (that.wildcard) {
          return false;
        }

        if (that.init_with_ip) {
          return (this.addresses[0].equals(that.addresses[0]));
        } else {
          if (that.addresses == null) {
            that.getIP();
          }
          for (i = 0; i < that.addresses.length; i++) {
            if (this.addresses[0].equals(that.addresses[i])) {
              return true;
            }
          }
        }
        // since "this" was initialized with an IP address, we
        // don't check any other cases
        return false;
      }

      // check and see if we have any wildcards...
      if (this.wildcard || that.wildcard) {
        // if they are both wildcards, return true iff
        // that's cname ends with this cname (i.e., *.sun.com
        // implies *.eng.sun.com)
        if (this.wildcard && that.wildcard) {
          return (that.cname.endsWith(this.cname));
        }

        // a non-wildcard can't imply a wildcard
        if (that.wildcard) {
          return false;
        }

        // this is a wildcard, lets see if that's cname ends with
        // it...
        if (that.cname == null) {
          that.getCanonName();
        }
        return (that.cname.endsWith(this.cname));
      }

      // comapare IP addresses
      if (this.addresses == null) {
        this.getIP();
      }

      if (that.addresses == null) {
        that.getIP();
      }

      if (!(that.init_with_ip && this.isUntrusted())) {
        for (j = 0; j < this.addresses.length; j++) {
          for (i = 0; i < that.addresses.length; i++) {
            if (this.addresses[j].equals(that.addresses[i])) {
              return true;
            }
          }
        }

        // XXX: if all else fails, compare hostnames?
        // Do we really want this?
        if (this.cname == null) {
          this.getCanonName();
        }

        if (that.cname == null) {
          that.getCanonName();
        }

        return (this.cname.equalsIgnoreCase(that.cname));
      }

    } catch (UnknownHostException uhe) {
      return compareHostnames(that);
    }

    // make sure the first thing that is done here is to return
    // false. If not, uncomment the return false in the above catch.

    return false;
  }

  private boolean compareHostnames(SocketPermission that) {
    // we see if the original names/IPs passed in were equal.

    String thisHost = hostname;
    String thatHost = that.hostname;

    if (thisHost == null) {
      return false;
    } else if (this.wildcard) {
      final int cnameLength = this.cname.length();
      return thatHost.regionMatches(true,
          (thatHost.length() - cnameLength),
          this.cname, 0, cnameLength);
    } else {
      return thisHost.equalsIgnoreCase(thatHost);
    }
  }

  /**
   * Checks two SocketPermission objects for equality.
   * <P>
   *
   * @param obj the object to test for equality with this object.
   * @return true if <i>obj</i> is a SocketPermission, and has the same hostname, port range, and
   * actions as this SocketPermission object. However, port range will be ignored in the comparison
   * if <i>obj</i> only contains the action, 'resolve'.
   */
  public boolean equals(Object obj) {
    if (obj == this) {
      return true;
    }

    if (!(obj instanceof SocketPermission)) {
      return false;
    }

    SocketPermission that = (SocketPermission) obj;

    //this is (overly?) complex!!!

    // check the mask first
    if (this.mask != that.mask) {
      return false;
    }

    if ((that.mask & RESOLVE) != that.mask) {
      // now check the port range...
      if ((this.portrange[0] != that.portrange[0]) ||
          (this.portrange[1] != that.portrange[1])) {
        return false;
      }
    }

    // short cut. This catches:
    //  "crypto" equal to "crypto", or
    // "1.2.3.4" equal to "1.2.3.4.", or
    //  "*.edu" equal to "*.edu", but it
    //  does not catch "crypto" equal to
    // "crypto.eng.sun.com".

    if (this.getName().equalsIgnoreCase(that.getName())) {
      return true;
    }

    // we now attempt to get the Canonical (FQDN) name and
    // compare that. If this fails, about all we can do is return
    // false.

    try {
      this.getCanonName();
      that.getCanonName();
    } catch (UnknownHostException uhe) {
      return false;
    }

    if (this.invalid || that.invalid) {
      return false;
    }

    if (this.cname != null) {
      return this.cname.equalsIgnoreCase(that.cname);
    }

    return false;
  }

  /**
   * Returns the hash code value for this object.
   *
   * @return a hash code value for this object.
   */

  public int hashCode() {
        /*
         * If this SocketPermission was initialized with an IP address
         * or a wildcard, use getName().hashCode(), otherwise use
         * the hashCode() of the host name returned from
         * java.net.InetAddress.getHostName method.
         */

    if (init_with_ip || wildcard) {
      return this.getName().hashCode();
    }

    try {
      getCanonName();
    } catch (UnknownHostException uhe) {

    }

    if (invalid || cname == null) {
      return this.getName().hashCode();
    } else {
      return this.cname.hashCode();
    }
  }

  /**
   * Return the current action mask.
   *
   * @return the actions mask.
   */

  int getMask() {
    return mask;
  }

  /**
   * Returns the "canonical string representation" of the actions in the
   * specified mask.
   * Always returns present actions in the following order:
   * connect, listen, accept, resolve.
   *
   * @param mask a specific integer action mask to translate into a string
   * @return the canonical string representation of the actions
   */
  private static String getActions(int mask) {
    StringBuilder sb = new StringBuilder();
    boolean comma = false;

    if ((mask & CONNECT) == CONNECT) {
      comma = true;
      sb.append("connect");
    }

    if ((mask & LISTEN) == LISTEN) {
      if (comma) {
        sb.append(',');
      } else {
        comma = true;
      }
      sb.append("listen");
    }

    if ((mask & ACCEPT) == ACCEPT) {
      if (comma) {
        sb.append(',');
      } else {
        comma = true;
      }
      sb.append("accept");
    }

    if ((mask & RESOLVE) == RESOLVE) {
      if (comma) {
        sb.append(',');
      } else {
        comma = true;
      }
      sb.append("resolve");
    }

    return sb.toString();
  }

  /**
   * Returns the canonical string representation of the actions.
   * Always returns present actions in the following order:
   * connect, listen, accept, resolve.
   *
   * @return the canonical string representation of the actions.
   */
  public String getActions() {
    if (actions == null) {
      actions = getActions(this.mask);
    }

    return actions;
  }

  /**
   * Returns a new PermissionCollection object for storing SocketPermission
   * objects.
   * <p>
   * SocketPermission objects must be stored in a manner that allows them
   * to be inserted into the collection in any order, but that also enables the
   * PermissionCollection {@code implies}
   * method to be implemented in an efficient (and consistent) manner.
   *
   * @return a new PermissionCollection object suitable for storing SocketPermissions.
   */

  public PermissionCollection newPermissionCollection() {
    return new SocketPermissionCollection();
  }

  /**
   * WriteObject is called to save the state of the SocketPermission
   * to a stream. The actions are serialized, and the superclass
   * takes care of the name.
   */
  private synchronized void writeObject(java.io.ObjectOutputStream s)
      throws IOException {
    // Write out the actions. The superclass takes care of the name
    // call getActions to make sure actions field is initialized
    if (actions == null) {
      getActions();
    }
    s.defaultWriteObject();
  }

  /**
   * readObject is called to restore the state of the SocketPermission from
   * a stream.
   */
  private synchronized void readObject(java.io.ObjectInputStream s)
      throws IOException, ClassNotFoundException {
    // Read in the action, then initialize the rest
    s.defaultReadObject();
    init(getName(), getMask(actions));
  }

  /**
   * Check the system/security property for the ephemeral port range
   * for this system. The suffix is either "high" or "low"
   */
  private static int initEphemeralPorts(String suffix, int defval) {
    return AccessController.doPrivileged(
        new PrivilegedAction<Integer>() {
          public Integer run() {
            int val = Integer.getInteger(
                "jdk.net.ephemeralPortRange." + suffix, -1
            );
            if (val != -1) {
              return val;
            } else {
              return suffix.equals("low") ?
                  PortConfig.getLower() : PortConfig.getUpper();
            }
          }
        }
    );
  }

  /**
   * Check if the target range is within the policy range
   * together with the ephemeral range for this platform
   * (if policy includes ephemeral range)
   */
  private static boolean inRange(
      int policyLow, int policyHigh, int targetLow, int targetHigh
  ) {
    final int ephemeralLow = EphemeralRange.low;
    final int ephemeralHigh = EphemeralRange.high;

    if (targetLow == 0) {
      // check policy includes ephemeral range
      if (!inRange(policyLow, policyHigh, ephemeralLow, ephemeralHigh)) {
        return false;
      }
      if (targetHigh == 0) {
        // nothing left to do
        return true;
      }
      // continue check with first real port number
      targetLow = 1;
    }

    if (policyLow == 0 && policyHigh == 0) {
      // ephemeral range only
      return targetLow >= ephemeralLow && targetHigh <= ephemeralHigh;
    }

    if (policyLow != 0) {
      // simple check of policy only
      return targetLow >= policyLow && targetHigh <= policyHigh;
    }

    // policyLow == 0 which means possibly two ranges to check

    // first check if policy and ephem range overlap/contiguous

    if (policyHigh >= ephemeralLow - 1) {
      return targetHigh <= ephemeralHigh;
    }

    // policy and ephem range do not overlap

    // target range must lie entirely inside policy range or eph range

    return (targetLow <= policyHigh && targetHigh <= policyHigh) ||
        (targetLow >= ephemeralLow && targetHigh <= ephemeralHigh);
  }
    /*
    public String toString()
    {
        StringBuffer s = new StringBuffer(super.toString() + "\n" +
            "cname = " + cname + "\n" +
            "wildcard = " + wildcard + "\n" +
            "invalid = " + invalid + "\n" +
            "portrange = " + portrange[0] + "," + portrange[1] + "\n");
        if (addresses != null) for (int i=0; i<addresses.length; i++) {
            s.append( addresses[i].getHostAddress());
            s.append("\n");
        } else {
            s.append("(no addresses)\n");
        }

        return s.toString();
    }

    public static void main(String args[]) throws Exception {
        SocketPermission this_ = new SocketPermission(args[0], "connect");
        SocketPermission that_ = new SocketPermission(args[1], "connect");
        System.out.println("-----\n");
        System.out.println("this.implies(that) = " + this_.implies(that_));
        System.out.println("-----\n");
        System.out.println("this = "+this_);
        System.out.println("-----\n");
        System.out.println("that = "+that_);
        System.out.println("-----\n");

        SocketPermissionCollection nps = new SocketPermissionCollection();
        nps.add(this_);
        nps.add(new SocketPermission("www-leland.stanford.edu","connect"));
        nps.add(new SocketPermission("www-sun.com","connect"));
        System.out.println("nps.implies(that) = " + nps.implies(that_));
        System.out.println("-----\n");
    }
    */
}

/**
 * if (init'd with IP, key is IP as string)
 * if wildcard, its the wild card
 * else its the cname?
 *
 * @author Roland Schemers
 * @serial include
 * @see java.security.Permission
 * @see java.security.Permissions
 * @see java.security.PermissionCollection
 */

final class SocketPermissionCollection extends PermissionCollection
    implements Serializable {

  // Not serialized; see serialization section at end of class
  private transient List<SocketPermission> perms;

  /**
   * Create an empty SocketPermissions object.
   */

  public SocketPermissionCollection() {
    perms = new ArrayList<SocketPermission>();
  }

  /**
   * Adds a permission to the SocketPermissions. The key for the hash is
   * the name in the case of wildcards, or all the IP addresses.
   *
   * @param permission the Permission object to add.
   * @throws IllegalArgumentException - if the permission is not a SocketPermission
   * @throws SecurityException - if this SocketPermissionCollection object has been marked readonly
   */
  public void add(Permission permission) {
    if (!(permission instanceof SocketPermission)) {
      throw new IllegalArgumentException("invalid permission: " +
          permission);
    }
    if (isReadOnly()) {
      throw new SecurityException(
          "attempt to add a Permission to a readonly PermissionCollection");
    }

    // optimization to ensure perms most likely to be tested
    // show up early (4301064)
    synchronized (this) {
      perms.add(0, (SocketPermission) permission);
    }
  }

  /**
   * Check and see if this collection of permissions implies the permissions
   * expressed in "permission".
   *
   * @param permission the Permission object to compare
   * @return true if "permission" is a proper subset of a permission in the collection, false if
   * not.
   */

  public boolean implies(Permission permission) {
    if (!(permission instanceof SocketPermission)) {
      return false;
    }

    SocketPermission np = (SocketPermission) permission;

    int desired = np.getMask();
    int effective = 0;
    int needed = desired;

    synchronized (this) {
      int len = perms.size();
      //System.out.println("implies "+np);
      for (int i = 0; i < len; i++) {
        SocketPermission x = perms.get(i);
        //System.out.println("  trying "+x);
        if (((needed & x.getMask()) != 0) && x.impliesIgnoreMask(np)) {
          effective |= x.getMask();
          if ((effective & desired) == desired) {
            return true;
          }
          needed = (desired ^ effective);
        }
      }
    }
    return false;
  }

  /**
   * Returns an enumeration of all the SocketPermission objects in the
   * container.
   *
   * @return an enumeration of all the SocketPermission objects.
   */

  @SuppressWarnings("unchecked")
  public Enumeration<Permission> elements() {
    // Convert Iterator into Enumeration
    synchronized (this) {
      return Collections.enumeration((List<Permission>) (List) perms);
    }
  }

  private static final long serialVersionUID = 2787186408602843674L;

  // Need to maintain serialization interoperability with earlier releases,
  // which had the serializable field:

  //
  // The SocketPermissions for this set.
  // @serial
  //
  // private Vector permissions;

  /**
   * @serialField permissions java.util.Vector A list of the SocketPermissions for this set.
   */
  private static final ObjectStreamField[] serialPersistentFields = {
      new ObjectStreamField("permissions", Vector.class),
  };

  /**
   * @serialData "permissions" field (a Vector containing the SocketPermissions).
   */
    /*
     * Writes the contents of the perms field out as a Vector for
     * serialization compatibility with earlier releases.
     */
  private void writeObject(ObjectOutputStream out) throws IOException {
    // Don't call out.defaultWriteObject()

    // Write out Vector
    Vector<SocketPermission> permissions = new Vector<>(perms.size());

    synchronized (this) {
      permissions.addAll(perms);
    }

    ObjectOutputStream.PutField pfields = out.putFields();
    pfields.put("permissions", permissions);
    out.writeFields();
  }

  /*
   * Reads in a Vector of SocketPermissions and saves them in the perms field.
   */
  private void readObject(ObjectInputStream in)
      throws IOException, ClassNotFoundException {
    // Don't call in.defaultReadObject()

    // Read in serialized fields
    ObjectInputStream.GetField gfields = in.readFields();

    // Get the one we want
    @SuppressWarnings("unchecked")
    Vector<SocketPermission> permissions = (Vector<SocketPermission>) gfields
        .get("permissions", null);
    perms = new ArrayList<SocketPermission>(permissions.size());
    perms.addAll(permissions);
  }
}
